%% ect.erl
%%   Entry point of ECT module loading interface. (Loads classes and generates proxies so that consistency is 
%%
%% Copyright (C) 2008, 2009 Gábor Fehér
%%
%% This file is part of ECT.
%% 
%% ECT is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% 
%% ECT is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%% 
%% You should have received a copy of the GNU General Public License
%% along with ECT.  If not, see <http://www.gnu.org/licenses/>.
%%
-module(ect).
-export([load/2]).

-include("common_defs.hrl").

%% Print messages when loading a class. (true/false)
-define(SILENT_LOAD,true).

%% Function: install_module(Forms, NameAtom)
%% Purpose: compile an abstract syntax tree, and load the resulting module
%% Args:
%%   Forms: abstract syntax tree to compile
%%   NameAtom: the resulting module will be loaded by this name
%% Side effects:
%%   If ?SILENT_LOAD is set to false, then Forms is dumped as a source code into a file.
%%   Forms is compiled.
%%   The result is loaded.
install_module(Forms, NameAtom) ->
    my_pp:print(Forms, atom_to_list(NameAtom)++".runtime.ectgen.erl", ?SILENT_LOAD),
    Binary = 
        case compile:forms(Forms) of
        {ok,NameAtom,BinaryOrCode} -> BinaryOrCode;
        {ok,NameAtom,BinaryOrCode, _Warnings} -> BinaryOrCode
        end,
    code:load_binary(NameAtom, "**ect**"++atom_to_list(NameAtom), Binary).

%% Function: install_proxy(Definition, CompileInfo)
%% Purpose: generates the proxy modules for a class and loads them
%% Args:
%%   Definition: definition of the class, for which the proxy is generated. (#class{})
%%   CompileInfo: general compiler and ECT options (#compile_info{})
%% Side effects:
%%   Generates and loads the proxy module of the class.
%%   Also generates and loads the method proxy in case of mode2.
install_proxy(Definition, CompileInfo1) ->
    CompileInfo2 = CompileInfo1#compile_info{class_definition = Definition},
    Proxies = gen_proxy:create_proxy(CompileInfo2),
    lists:map(
        fun ({Forms, Name}) -> 
            install_module(Forms, Name)
        end,
        Proxies).

%% Function: install_proxy(Definition)
%% Purpose: returns the list of loaded subclasses of a loaded class.
%% Args:
%%   Definition: Definition of the class, for which the loaded subclasses have to be listed.
%%               It is a record of #class{}.
%% Returns:
%%   A copy Definition with its field loaded_subclasses set to the list of loaded 
%%   subclasses.
get_loaded_subclasses(ClassDef = #class{name = Name}) ->
    case modules:is_available(Name, memory) of
    true ->
        OldClassAttrs = modules:get_attributes(Name, memory),
        {loaded_subclasses,LSC} = proplists:lookup(loaded_subclasses, OldClassAttrs),
        ClassDef#class{loaded_subclasses = LSC};
    false ->
        ClassDef
    end.

%% Function: propagate_upgrade(Definition, CompileInfo)
%% Purpose: upgrades the proxy modules of a given class, and all its subclasses
%% Args:
%%   Definition: definition of the class, for which the proxy is generated. (#class{})
%%   CompileInfo: general compiler and ECT options (#compile_info{})
%% Side effects:
%%   Generates and loads new proxy (and method proxy in case of mode2) modules for the class
%%   specified by definition. And propagates the changes in the class to all the proxies 
%%   of its subclasses.
propagate_upgrade(ClassDef = #class{loaded_subclasses = SubClasses}, CompileInfo) ->
    propagate_upgrade(ClassDef, SubClasses, CompileInfo).
propagate_upgrade(_, [], _) ->
    ok;
propagate_upgrade(ClassDef, [SubClass|SubClasses], CompileInfo) ->
    {LocalSubClassDef, SubSubClasses}  = modules:get_local_classdata(SubClass, proxy, memory),
    SubClassDef = class_defs:append_ldef([LocalSubClassDef], ClassDef),
    install_proxy(SubClassDef#class{loaded_subclasses = SubSubClasses}, CompileInfo),
    propagate_upgrade(SubClassDef, SubSubClasses, CompileInfo),
    propagate_upgrade(ClassDef, SubClasses, CompileInfo).

%% Function: register_subclass_at_superclass(Definition, ClassName, CompileInfo)
%% Purpose: adds a new class to the list of loaded subclasses in a class proxy module.
%% Args:
%%   Definition: definition of the class, for which a new subclass is added.
%%   ClassName: name of new subclass to add
%%   CompileInfo: general compiler and ECT options (#compile_info{})
%% Side effects:
%%   Generates and loads new proxy modules for the class of Definition, with class
%%   ClassName added as a subclass.
register_subclass_at_superclass(#class{loaded_subclasses = SubClasses1} = Def1, ClassName, CompileInfo) ->
    Def2 = 
        Def1#class{
            loaded_subclasses = misc:add_to_list(ClassName, SubClasses1)
        },
    install_proxy(Def2, CompileInfo).

%% Function: collect_definition(Class)
%% Purpose: Collects all the information about a class that resides in memory.
%% Args:
%%   Class: name of class (an atom)
%% Returns:
%%   {[#class_local{}], [LoadedSubClass]} where
%%     the first item is a list of local data about all the superclasses of the Class, and the Class itself
%%     the second item is the list of loaded subclasses of the class
collect_definition(Class) ->
    collect_definition(Class, [], unknown).
collect_definition({ok, Class}, LDef, LSC1) ->
    {LocalClassData, LSC2} = modules:get_local_classdata(Class, proxy, memory),
    collect_definition(
        LocalClassData#class_local.super_class,
        [LocalClassData|LDef],
        case LSC1 of unknown -> LSC2; _ -> LSC1 end
    );
collect_definition(none, LDef, LSC) ->
    {LDef, LSC}.

%% Function: load(Class, UpgradeMode)
%% Purpose: loads the implementation module of a class, and generates proxy modules
%% Args:
%%   Class: name of implementation module
%%   UpgradeMode: either mode1 or mode2
%% Returns: the atom 'ok'
%% Side effects:
%%   Loads the module specified by Class, generates proxy module for it. (and method proxy in mode2).
%%   Upgrades proxy/method proxy modules of other loaded classes. 
%% Throws exception in case of problems.
load0(Class, UpgradeMode) ->
    CompileInfo = #compile_info{cctrans = cctrans, upgrade_mode = UpgradeMode},

    {LocalClassData2, _} = modules:get_local_classdata(Class, impl, disk),
    
    
    SuperClass = LocalClassData2#class_local.super_class,
    ClassDef1 =
        case SuperClass of
        {ok, _} ->
            {SuperClassLDef, LSC} = collect_definition(SuperClass),
            SCD1 = class_defs:assemble_ldef(lists:reverse(SuperClassLDef)),  % FIXME 2x reverse...
            SCD2 = SCD1#class{loaded_subclasses = LSC},
            register_subclass_at_superclass(SCD2, LocalClassData2#class_local.name, CompileInfo),
            class_defs:append_ldef([LocalClassData2], SCD2);
        none ->
            class_defs:assemble_ldef([LocalClassData2])
        end,
    ClassDef2 = get_loaded_subclasses(ClassDef1),

    
    code:load_file(Class),
    install_proxy(ClassDef2, CompileInfo),
    
    propagate_upgrade(ClassDef2, CompileInfo).
    
%% Function: load(Class, UpgradeMode)
%% Purpose: loads the implementation module of a class, and generates proxy modules
%% Args:
%%   Class: name of implementation module
%%   UpgradeMode: either mode1 or mode2
%% Returns: the atom 'ok'
%% Side effects:
%%   Loads the module specified by Class, generates proxy module for it. (and method proxy in mode2).
%%   Upgrades proxy/method proxy modules of other loaded classes. Prints error message in case of 
%%   problems.
load(Class, UpgradeMode) ->
    try
        load0(Class, UpgradeMode),
        ok
    catch
        throw:{ect_error, Err} ->
            io:format("Error while loading:~n"++Err++"~n", [])
    end.